Skip to content

fix(webhooks): materialize stack-declared webhooks into the dispatcher (#3461)#3489

Merged
os-zhuang merged 1 commit into
mainfrom
fix/3461-webhook-bridge
Jul 25, 2026
Merged

fix(webhooks): materialize stack-declared webhooks into the dispatcher (#3461)#3489
os-zhuang merged 1 commit into
mainfrom
fix/3461-webhook-bridge

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3461.

Problem

The spec WebhookSchema authoring surface was disconnected from the runtime dispatcher — not merely a naming drift. A webhook authored declaratively (defineStack({ webhooks }) / defineWebhook(), declaring object / isActive) is decomposed into the ObjectQL registry as webhook metadata, but the dispatcher (AutoEnqueuer) fans out off sys_webhook data rows (object_name / active) that until now were only ever written by hand through the object's CRUD UI. No ingestion path turned a declared webhook into a dispatchable row, so authoring webhooks: on a stack was a silent no-op (ADR-0078). The showcase app itself shipped a webhooks: entry that did nothing — and, separately, never required the webhooks capability, so the dispatcher plugin wasn't even mounted.

Per the issue's decision, this is Option A — build the bridge (Option B would retire a public authoring surface the showcase already uses).

What changed

  • bootstrap-declared-webhooks.ts — reads declared webhook metadata from the registry (falling back to the metadata service), validates each through WebhookSchema.parse() (the spec schema finally has a real consumer + default-fill), and materializes a sys_webhook row: object → object_name, isActive → active, full validated envelope → definition_json (whence the enqueuer reads headers/secret/timeout). Modeled on the sibling bootstrapDeclaredSharingRules.
  • Runs on the data engine alone, before the auto-enqueuer's first cache refresh — deliberately not gated behind the realtime/messaging dispatch prerequisites. (An earlier revision gated it inside bootAutoEnqueue, which meant a realtime-less deployment silently failed to materialize — the very no-op class this closes. Caught during the real-boot dogfood.)
  • Seed-not-clobber provenance (mirrors sys_sharing_rule, Audit sibling declared-metadata↔record two-store types (sys_position, sys_sharing_rule, sys_capability) per ADR-0094 addendum #2909): sys_webhook gains managed_by / customized. Declared webhooks re-seed every boot as managed_by: 'package'; a row an admin created (admin) or edited (customized, stamped by a beforeUpdate hook in webhook-provenance.ts) is never overwritten — a deactivated noisy webhook survives redeploys.
  • showcase — require the webhooks + realtime capabilities so the dispatcher actually mounts, and ship the demo webhook inactive (the hooks.example URL is a placeholder; activate it in Setup).
  • specWebhookSchema docstring documents the materialization contract. Connector webhooks remain not-yet-enforced (Audit: several event/subscription/connector enums are schema-only (declared, no runtime consumer) #3197).
  • Fixed a pre-existing dangling SysWebhookDelivery import in the i18n extract config (dead since delivery moved to service-messaging).

Verification

9 new unit tests (bootstrap-declared-webhooks.test.ts) — mapping, idempotency, declared-change propagation, seed-not-clobber, admin-name-collision, invalid-webhook skip, no-op-when-empty, and end-to-end dispatch (declared → materialized → AutoEnqueuer fires with headers/secret from definition_json). Red-proofed (breaking the mapping + the skip guard fails the right tests). Full suite: 23 passing.

Real showcase boot (objectstack dev, SQLite):

  • declared webhook materializes into a sys_webhook row — object_name=showcase_task, active=0, managed_by=package
  • same-DB reboot stays idempotent (1 row, not duplicated) ✅
  • an admin's customized edit (active=1) survives redeploy despite the declared isActive:false

Build + DTS type-check green.

Follow-ups (out of scope)

🤖 Generated with Claude Code

#3461)

The spec `WebhookSchema` authoring surface (`defineStack({ webhooks })` /
`defineWebhook()`, `object` / `isActive`) was disconnected from the runtime
dispatcher, which fans out off `sys_webhook` DATA rows (`object_name` /
`active`) written only by hand through the object's CRUD UI. Nothing turned a
declared webhook into a dispatchable row, so authoring `webhooks:` on a stack
was a silent no-op (ADR-0078) — the showcase itself shipped one that did
nothing.

- `bootstrapDeclaredWebhooks` reads declared `webhook` metadata from the
  ObjectQL registry (where manifest decomposition already parks
  `stack.webhooks`), validates each through `WebhookSchema.parse()` (the spec
  schema finally gets a real consumer), and materializes it into a `sys_webhook`
  row: `object → object_name`, `isActive → active`, full envelope →
  `definition_json`. Runs on the DATA ENGINE alone, before the auto-enqueuer's
  first refresh — NOT gated behind the realtime/messaging dispatch prerequisites
  (else a realtime-less deployment reproduces the silent no-op).
- Seed-not-clobber provenance (mirrors sys_sharing_rule #2909): `sys_webhook`
  gains `managed_by` / `customized`. Declared webhooks re-seed as
  `managed_by: 'package'`; a row an admin created (`admin`) or edited
  (`customized`, stamped by a beforeUpdate hook) is never overwritten.
- showcase: require the `webhooks` + `realtime` capabilities (so the dispatcher
  actually mounts) and ship the demo webhook inactive (placeholder endpoint).
- Fix the stale `SysWebhookDelivery` import in the i18n extract config (dead
  since delivery moved to service-messaging).

Connector `webhooks` remain not-yet-enforced (#3197). Registering `webhook` as a
metadata type + GOVERNED liveness enrollment is a tracked follow-up.

Verified: 9 new unit tests (mapping / idempotency / seed-not-clobber / invalid /
end-to-end dispatch), red-proofed; and a real showcase boot — declared webhook
materializes into a sys_webhook row, same-DB reboot stays idempotent, and an
admin's customized edit survives redeploy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment Jul 25, 2026 3:07am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-webhooks, @objectstack/spec.

104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via packages/plugins/plugin-webhooks, @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-webhooks, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-webhooks, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] Webhook: the spec WebhookSchema authoring surface is disconnected from the sys_webhook dispatcher

1 participant